/* lorina: C++ parsing library
 * Copyright (C) 2018-2021  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/*!
  \file super.hpp
  \brief Implements SUPER parser for files generated by ABC

  \author Alessandro Tempia Calvino
  \author Shubham Rai
*/

#pragma once

#include "common.hpp"
#include "detail/utils.hpp"
#include "diagnostics.hpp"
#include <fstream>
#include <istream>
#include <optional>
#include <sstream>
#include <string>

namespace lorina
{

/*! \brief A reader visitor for a super format.
 *
 * Callbacks for the super format.
 */
class super_reader
{
public:
  virtual void on_super_info( std::string const& genlib_name, uint32_t max_num_vars, uint32_t max_supergates, uint32_t num_lines ) const
  {
    (void) genlib_name;
    (void) max_num_vars;
    (void) max_supergates;
    (void) num_lines;
  }

  virtual void on_supergate( std::string const& name, bool const& is_super, std::vector<uint32_t> const& fanins_id ) const
  {
    (void)name;
    (void)is_super;
    (void)fanins_id;
  }
}; /* super_reader */

/*! \brief Parse for the SUPER format.
 *
 */
class super_parser
{
public:
  explicit super_parser( std::istream& in, super_reader const& reader, diagnostic_engine* diag )
      : in( in )
      , reader( reader )
      , diag( diag )
  {
  }

public:
  bool run()
  {
    std::string line;
    uint32_t info_lines = 0u;
    uint32_t max_num_vars = 0u;

    std::vector<std::string> info_vec;

    while ( std::getline( in, line ) )
    {
      /* remove whitespaces */
      detail::trim( line );

      /* skip comments and empty lines */
      if ( line[0] == '#' || line.empty() )
      {
        continue;
      }

      /* end of file char */
      if ( line[0] == '\0' )
      {
        return true;
      }

      if ( info_lines < 4 )
      {
        if ( !parse_file_info( line, info_vec ) )
        {
          return false;
        }
        if ( info_lines == 1 )
        {
          max_num_vars = std::stod( info_vec[1] );
        }
        ++info_lines;
      }
      else
      {
        if ( !parse_gate_definition( line, max_num_vars ) )
        {
          return false;
        }
      }
    }
    return true;
  }

private:
  bool parse_file_info( std::string const& line, std::vector<std::string>& info_vec )
  {
    std::stringstream ss( line );
    std::string const deliminators{ " \t\r\n" };
    std::string token;

    std::vector<std::string> tokens;

    while ( std::getline( ss, token, '\n' ) )
    {
      tokens.emplace_back( token );
      info_vec.emplace_back( token );
    }

    if ( tokens.size() > 2 )
    {
      if ( diag )
      {
        diag->report( diag_id::ERR_SUPER_INFO ).add_argument( line );
      }
      return false;
    }

    if ( info_vec.size() == 4 )
    {
      reader.on_super_info( info_vec[0], std::stoi( info_vec[1] ), std::stoi( info_vec[2] ), std::stoi( info_vec[3] ) );
    }

    return true;
  }

  bool parse_gate_definition( std::string const& line, uint32_t const& max_num_vars )
  {
    std::stringstream ss( line );
    std::string const deliminators{ " \t\r\n" };

    std::string token;
    std::vector<std::string> tokens;

    std::string name;
    std::vector<uint32_t> fanins_id;

    while ( std::getline( ss, token ) )
    {
      std::size_t prev = 0, pos;
      while ( ( pos = line.find_first_of( deliminators, prev ) ) != std::string::npos )
      {
        if ( pos > prev )
        {
          tokens.emplace_back( token.substr( prev, pos - prev ) );
        }
        prev = pos + 1;
      }

      if ( prev < line.length() )
      {
        tokens.emplace_back( token.substr( prev, std::string::npos ) );
      }
    }

    if ( tokens.size() < 2 || tokens.size() > max_num_vars + 2 )
    {
      if ( diag )
      {
        diag->report( diag_id::ERR_SUPER_UNEXPECTED_STRUCTURE ).add_argument( line );
      }
      return false;
    }

    bool is_super = false;
    uint64_t i{2};
    if ( tokens[0] == "*" )
    {
      is_super = true;
      name = tokens[1];
    }
    else
    {
      name = tokens[0];
      i = 1u;
    }

    for ( auto j = i; j < tokens.size(); ++j )
    {
      fanins_id.emplace_back( std::stod( tokens[j] ) );
    }

    if ( fanins_id.size() == 0 )
    {
      if ( diag )
      {
        diag->report( diag_id::ERR_SUPER_GATE ).add_argument( line );
      }
      return false;
    }

    reader.on_supergate( name, is_super, fanins_id );
    return true;
  }

protected:
  std::istream& in;
  super_reader const& reader;
  diagnostic_engine* diag;
}; /* super_parser */

/*! \brief Reader function for the SUPER format.
 *
 * Reads SUPER format from a stream and invokes a callback
 * method for each parsed primitive and each detected parse error.
 *
 * \param in Input stream
 * \param reader SUPER reader with callback methods invoked for parsed primitives
 * \param diag An optional diagnostic engine with callback methods for parse errors
 * \return Success if parsing has been successful, or parse error if parsing has failed
 */
[[nodiscard]] inline return_code read_super( std::istream& in, const super_reader& reader, diagnostic_engine* diag = nullptr )
{
  super_parser parser( in, reader, diag );
  auto result = parser.run();
  if ( !result )
  {
    return return_code::parse_error;
  }
  else
  {
    return return_code::success;
  }
}

/*! \brief Reader function for the SUPER format.
 *
 * Reads SUPER format from a .super file generated by ABC and invokes a callback
 * method for each parsed primitive and each detected parse error.
 *
 * \param filename Name of the file
 * \param reader SUPER reader with callback methods invoked for parsed primitives
 * \param diag An optional diagnostic engine with callback methods for parse errors
 * \return Success if parsing has been successful, or parse error if parsing has failed
 */
[[nodiscard]] inline return_code read_super( const std::string& filename, const super_reader& reader, diagnostic_engine* diag = nullptr )
{
  std::ifstream in( detail::word_exp_filename( filename ), std::ifstream::in );
  if ( !in.is_open() )
  {
    if ( diag )
    {
      diag->report( diag_id::ERR_FILE_OPEN ).add_argument( filename );
    }
    return return_code::parse_error;
  }
  else
  {
    auto const ret = read_super( in, reader, diag );
    in.close();
    return ret;
  }
}

} // namespace lorina
